#include <iostream>
using namespace std;
#include "GL/glut.h"
#include <fstream>
#include "Joueur.h"
#include "EnnemiVert.h"
#include "EnnemiRouge.h"
#include "EnnemiBase.h"
#include <time.h>


int NbColonnes, NbLignes; // Taille du niveau
int SortieC = 0, SortieL = 0; // Coordonnes de sortie du niveau
char** Matrice;  // Matrice contenant le niveau
Joueur monJoueur;  // Objet global de type Joueur
EnnemiBase* premierEnnemi = NULL; // Pointeur sur le premier ennemi de la liste

const int TIMER_MILLIS=500; // Laps de temps entre 2 appels  l'vnement Timer


// Enumration pour le type d'ennemi
enum TYPE_ENNEMI
{
    ENNEMI_VERT,    // Identificateur de l'ennemi vert
    ENNEMI_ROUGE    // Identificateur de l'ennemi rouge
};



// Dclarations de fonctions :
void LabyAffichage();
void LabyRedim(int x, int y);
void LabyClavierSpecial(int key, int x, int y);
void LabyTimer(int value);

void OuvrirNiveau(char* nom_fichier);
void LibererMemoire();
void DessinerNiveau();
void TestVictoire();
void AjouterEnnemi(TYPE_ENNEMI type,int Colonne,int Ligne);


// Fonction principale
void main(void)
{
	// amorage du "pseudo-alatoire" (dans la fonction main)
	srand((int)time(NULL));

    NbColonnes=NbLignes=0;	// initialisation de la taille
	Matrice = NULL;			// initialisation de la matrice

    /* Gestion de graphique */
    // position de la fentre
    glutInitWindowPosition(10, 10);
    // taille de la fentre
    glutInitWindowSize(500, 500);
    // Mode d'affichage
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); // double buffering
    // cration de la fentre
    glutCreateWindow("Labyrinthe");

    // fonction d'affichage
    glutDisplayFunc(LabyAffichage);
    // fonction de redimentionnement
    glutReshapeFunc(LabyRedim);
	// touches spciales du clavier
	glutSpecialFunc(LabyClavierSpecial);
	// fonction timer
	glutTimerFunc(TIMER_MILLIS, LabyTimer, 0);

	// Ouverture de "niveau.txt"
	OuvrirNiveau("niveau.txt");

    glutMainLoop();
}


// Evnement d'affichage
void LabyAffichage()
{
    // dfinit la couleur de fond
    glClearColor(1.0, 1.0, 1.0, 1.0);
    // efface l'cran
    glClear(GL_COLOR_BUFFER_BIT);
    // dfinit la matrice de modlisation active
    glMatrixMode(GL_MODELVIEW);

	// Affiche le niveau
    DessinerNiveau();

	// Affiche l'avatar du joueur
	monJoueur.Dessiner();

	// Affichage des ennemis de la liste
	EnnemiBase* Pointeur = premierEnnemi;
	while(Pointeur != NULL) // Si le pointeur pointe sur quelque chose d'existant
	{
		// Affichage de l'ennemi courant
		Pointeur->Dessiner();
		// Passage  l'ennemi suivant
		Pointeur = Pointeur->GetSuivant();
	}

    // achve l'affichage et inverse les deux tampons
    glutSwapBuffers();
}


// Dessin du dcor
void DessinerNiveau()
{
    // Dessin des murs :
    glColor3d(0.5, 0.5, 0.5);  // Couleur grise
    // Commence l'affichage de quadrilatres
    glBegin(GL_QUADS);
    // Parcourt toutes les cellules de la matrice
    for(int i=0; i<NbColonnes; i++)
        for(int j=0; j<NbLignes; j++)
            // Si c'est un mur, on affiche un carr
            if(Matrice[i][j] == '0')
            {
                // Place les points du carr :
                glVertex2d(i,   j);
                glVertex2d(i,   j+1);
                glVertex2d(i+1, j+1);
                glVertex2d(i+1, j);
            }
    glEnd();   // Achve l'affichage

    // Affichage du point de sortie en vert :
    glPushMatrix();
    // Se positionne au point de sortie
    glTranslated(SortieC+0.5, SortieL+0.5, 0.0);
        glColor3d(0.3, 1.0, 0.3);  // couleur verte
        // Dessin de plusieurs carrs de taille croissante :
        for(double taille=0.1; taille<1.0; taille+=0.2)
            // Dessine un carr de taille "taille"
            glutWireCube(taille);
    glPopMatrix();
}


// Evnement de redimensionnement
void LabyRedim(int x, int y)
{
    glViewport(0, 0, x, y);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, (double)NbColonnes,
               (double)NbLignes, 0.0);
}


// Evnement lors de l'appui sur une touche spciale du clavier
void LabyClavierSpecial(int key, int x, int y)
{
	switch(key) {
	case GLUT_KEY_UP: {
		// dplacer le joueur vers le haut
		monJoueur.BougerEnHaut();
		break;}
	case GLUT_KEY_DOWN: {
		// dplacer le joueur vers le bas
		monJoueur.BougerEnBas();
		break;}
	case GLUT_KEY_LEFT: {
		// dplacer le joueur vers la gauche
		monJoueur.BougerAGauche();
		break;}
	case GLUT_KEY_RIGHT: {
		// dplacer le joueur vers la droite
		monJoueur.BougerADroite();
		break;}
	}

	// le joueur a peut tre gagn
	TestVictoire();

	// On regarde si le joueur a t mang par un des ennemis de la liste :
	EnnemiBase* Pointeur = premierEnnemi;
	while(Pointeur != NULL)  // Si la liste n'est pas vide
	{
		// Test des collisions avec le joueur
		Pointeur->TestCollision();
		// Passage  l'ennemi suivant
		Pointeur = Pointeur->GetSuivant();
	}


	// ordre de rafraichir l'affichage
	glutPostRedisplay();
}

// Fonction Timer appele toutes les TIMER_MILLIS millisecondes
void LabyTimer(int value)
{
	// Dplacement de tous les ennemis de la liste :
    EnnemiBase* Pointeur = premierEnnemi;
    while(Pointeur != NULL)  // Si la liste n'est pas vide
    {
        // Dplacement de l'ennemi courant
        Pointeur->DeplacementAuto();
        // Test des collisions avec le joueur
        Pointeur->TestCollision();
        // Passage  l'ennemi suivant
        Pointeur = Pointeur->GetSuivant();
    }

    // Ordre de rafraichir l'affichage
    glutPostRedisplay();
    
	// Pour que la fonction Timer soit rpte
    glutTimerFunc(TIMER_MILLIS, LabyTimer, 0);
}






// ouverture du niveau
void OuvrirNiveau(char* nom_fichier)
{
    ifstream fichier;           // Objet de type ifstream
    fichier.open(nom_fichier);  // Ouverture du fichier

    if(fichier == 0) {  // Test de l'existence du fichier
        cout << "Erreur lors de l'ouverture du fichier !"
             << endl;
        system("pause");
        exit(1);
    }

    // Lecture de la taille du niveau :
    fichier >> NbColonnes;
    fichier >> NbLignes;

    // Allocation du tableau du niveau :
    Matrice = new char*[NbColonnes];
    for(int i=0; i<NbColonnes; i++)
        Matrice[i] = new char[NbLignes];

    // Initialisation des valeurs du tableau :
    for(int i=0; i<NbColonnes; i++)
        for(int j=0; j<NbLignes; j++)
            Matrice[i][j] = '0';

    // Lecture du tableau du niveau, caractre par caractre
    for(int j=0; j<NbLignes; j++)
		for(int i=0; i<NbColonnes; i++)
		{
			fichier >> Matrice[i][j];  // Lecture du caractre

			switch(Matrice[i][j])      // Test du caractre lu
			{
			// Position initiale de joueur
			case 'j':
			case 'J': {
				monJoueur.SetPosC(i);
				monJoueur.SetPosL(j);
				break;
				}

			// Sortie de jeu
			case 's':
			case 'S': {
				SortieC = i;
				SortieL = j;
				break;
				}

			// Ajout d'un Ennemi Vert
			case 'v':
			case 'V': {
				AjouterEnnemi(ENNEMI_VERT, i, j);
				break;
				}

			// Ajout d'un Ennemi Rouge
			case 'r':
			case 'R': {
				AjouterEnnemi(ENNEMI_ROUGE, i, j);
				break;
				}
			}
		}

    fichier.close();    // Fermeture du fichier
}

// libration de mmoire
void LibererMemoire()
{
    /* Suppression de la matrice du niveau */
    // Vrifie que Matrice ait bien t alloue
    if(Matrice != NULL)
    {
        for(int i=0; i<NbColonnes; i++)
            // Libration des colonnes
            delete [] Matrice[i];
        // Libration de la ligne de pointeurs
        delete [] Matrice;
    }

    /* Suppression de la liste chane d'ennemis */
    // Pointeur temporaire
    EnnemiBase* Pointeur = NULL;
    while(premierEnnemi != NULL)  // Si l'lment existe
    {
        // Fait pointer Pointeur sur l'lment courant
        Pointeur = premierEnnemi;
        // Sauvegarde du suivant
        premierEnnemi = premierEnnemi->GetSuivant();
        // Suppression de l'lment en cours
        delete Pointeur;
    }
}


// Fonction testant si le joueur est sur la case de sortie ou non
void TestVictoire()
{
    // Comparaison de la position du joueur
    // avec le point de sortie :
    if(monJoueur.GetPosC()==SortieC &&
       monJoueur.GetPosL()==SortieL)
    {
        cout << "Vous avez gagne !" << endl;
        LabyAffichage();   // Rafrachit la scne
        LibererMemoire();  // Libre la mmoire alloue
        system("pause");
        exit(1);
    }
}

// Ajout d'un ennemi  la liste
void AjouterEnnemi(TYPE_ENNEMI type, int Colonne, int Ligne)
{
    EnnemiBase* Pointeur = NULL;
    switch(type) {
    case ENNEMI_VERT:  Pointeur = new EnnemiVert();  break;
    case ENNEMI_ROUGE: Pointeur = new EnnemiRouge(); break;
    default: exit(0); // Type inconnu : on quitte le jeu
    }

    Pointeur->SetPosC(Colonne); // On dfinit la position de
    Pointeur->SetPosL(Ligne);   // l'ennemi fraichement cr

    // Place le nouvel ennemi en tte de liste
    Pointeur->SetSuivant(premierEnnemi);
    // La nouvelle tte est le nouvel ennemi
    premierEnnemi = Pointeur;
}



